package com.funi;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSON;
import com.funi.bo.*;
import com.funi.consts.Constants;
import com.funi.utils.HttpConnectUtils;
import com.funi.utils.ListToJsonFileWithJackson;
import com.funi.vo.DeveloperContributedCodeLineStatResult;
import lombok.extern.slf4j.Slf4j;
import org.gitlab4j.api.GitLabApi;
import org.gitlab4j.api.GitLabApiException;
import org.gitlab4j.api.models.*;
import org.junit.Test;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.stream.Collectors;

/**********************************************************************
 * &lt;p&gt;文件名：FuniGitLabCodeCommitStats.java &lt;/p&gt;
 * &lt;p&gt;文件描述：用户业务对象
 * @project_name：code_total
 * @author zengshunyao
 * @create 2025/10/01 13:12
 * @history
 * @department：政务事业部
 * Copyright ChengDu Funi Cloud Code Technology Development CO.,LTD 2014
 *                    All Rights Reserved.
 */
@Slf4j
public class FuniGitLabCodeCommitStats {
    /**
     * 常量定义 (GITLAB_URL, PERSONAL_ACCESS_TOKEN, TIME_RANGES, CD_GROUP_MEMBERS) 到 {@link Constants}
     * 创建服务类实例,项目服务实例、用户服务实例、代码提交服务实例
     */
    private final static ProjectService projectService = new ProjectService(Constants.GitLabAPI);
    private final static UserService userService = new UserService(Constants.GitLabAPI);
    private final static CommitStatsService commitStatsService = new CommitStatsService(Constants.GitLabAPI);

    /**
     * 统计某个开发者在指定时间范围内贡献的代码行数
     * 预计耗时20-30分钟,请务必设置计算时间范围、缩小人员范围,耐心等待计算结果。
     * 时间范围
     * 开始时间 {@link Constants#START_TIME_STR}
     * 结束时间 {@link Constants#END_TIME_STR}
     * 人员范围 {@link com.funi.consts.Constants#CD_GROUP_MEMBER_USERNAME_LIST}
     */
    @Test
    public void authorContributedCodeLineCount() throws Exception {
        final long startTime = System.currentTimeMillis();
        log.info("运行开始时间: {}", DateUtil.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));

        try {
            //启用日志
            Constants.GitLabAPI.enableRequestResponseLogging(Level.ALL);

            //0.网络连通性验证和API调用权限
            HttpConnectUtils.testGitLabHomepageAccess(Constants.GITLAB_URL);
            HttpConnectUtils.testGitLabApiCall(Constants.GITLAB_API_ENDPOINT, Constants.PERSONAL_ACCESS_TOKEN);

            //存储计算结果
            final List<DeveloperContributedCodeLineStatResult> results = new ArrayList<>(Constants.CD_GROUP_MEMBER_USERNAME_LIST.size());

            // 1. 获取所有活跃开发者和项目
            log.info("获取所有活跃开发者和项目:");
            final long startTime1 = System.currentTimeMillis();
            final ProjectsAndUsersBo projectsAndUsersBo = userService.getAllActiveDevelopersAndProjects();
            //log.info("✅ 所有活跃开发者列表: {}", JSON.toJSONString(projectsAndUsersBo.getAllUsers()));
            //log.info("✅ 所有活跃项目列表: {}", JSON.toJSONString(projectsAndUsersBo.getAllProjects()));
            log.info("✅ 所有活跃开发者总数: {} 个", (projectsAndUsersBo.getAllUsers()).size());
            log.info("✅ 所有活跃项目总数: {} 个", (projectsAndUsersBo.getAllProjects()).size());
            final long costTime1 = System.currentTimeMillis() - startTime1;
            log.info("✅ 获取所有活跃开发者和项目,耗时: {} 秒", TimeUnit.MILLISECONDS.toSeconds(costTime1));

            // 2. 筛选成都研发部成员
            log.info("筛选成都研发部成员:");
            final long startTime2 = System.currentTimeMillis();
            final List<User> cdGroupUsers = userService.filterChengduDevelopers(projectsAndUsersBo.getAllUsers(), Constants.CD_GROUP_MEMBER_USERNAME_LIST);
            final long costTime2 = System.currentTimeMillis() - startTime2;
            log.info("✅ 筛选成都研发部成员,耗时: {} 秒", TimeUnit.MILLISECONDS.toSeconds(costTime2));

            // 3. 获取指定时间范围内有活动的项目
            log.info("获取指定时间范围内有活动的项目:");
            final long startTime3 = System.currentTimeMillis();
            final List<Project> allProjects = projectsAndUsersBo.getAllProjects();
            final List<ProjectBo> activeProjects = projectService.getProjectsWithCommitsInTimeRange(allProjects, Constants.START_TIME, Constants.END_TIME);
            log.info("✅ 在指定时间范围内([{}-{}))有代码提交活动的项目数量: {} 个", Constants.START_TIME_STR, Constants.END_TIME_STR, activeProjects.size());
            final long costTime3 = System.currentTimeMillis() - startTime3;
            log.info("✅ 在指定时间范围内([{}-{}))有活动的项目,耗时: {} 秒", Constants.START_TIME_STR, Constants.END_TIME_STR, TimeUnit.MILLISECONDS.toSeconds(costTime3));

            // 4. 统计成都研发部的每个成员的代码贡献
            log.info("统计成都研发部的每个成员的代码贡献:");
            final long startTime4 = System.currentTimeMillis();
            for (final User developer : cdGroupUsers) {
                final DeveloperContributedCodeLineStatResult result = commitStatsService.calculateDeveloperCodeStats(developer, activeProjects, Constants.START_TIME, Constants.END_TIME, Constants.CD_GROUP_MEMBER_USERNAME_LIST);
                if (result != null) {
                    results.add(result);
                }
            }
            final long costTime4 = System.currentTimeMillis() - startTime4;
            log.info("✅ 为每个成都研发部的成员统计代码贡献,耗时: {} 秒", TimeUnit.MILLISECONDS.toSeconds(costTime4));

            // 5. 输出代码提交量排行榜
            log.info("统计范围,GIT仓库地址：{},指定时间段：[{} - {})", Constants.GITLAB_URL, Constants.START_TIME, Constants.END_TIME);
            final long startTime5 = System.currentTimeMillis();
            log.info("==========================================>代码提交量排行榜<==========================================");
            results.stream()
                    //倒叙排序
                    .sorted(Comparator.comparing(DeveloperContributedCodeLineStatResult::getTotalCode).reversed())
                    .forEach(result -> log.info(
                            "姓名： {}, Email： {}, 新增: {}行, 删除: {}行, 总计: {}行",
                            result.getName(),
                            result.getEmail(),
                            result.getAddCode(),
                            result.getDelCode(),
                            result.getTotalCode()
                    ));
            //输出到文件留底
            //定义输出文件路径
            final String filePath = Constants.OUTPUT_DIR + DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss") + "代码提交量排行榜.json";
            ListToJsonFileWithJackson.printCollectJsonStringToFile(filePath, results.stream()
                    .map(result -> String.format(
                            "姓名： %s, Email： %s, 新增: %d行, 删除: %d行, 总计: %d行",
                            result.getName(),
                            result.getEmail(),
                            result.getAddCode(),
                            result.getDelCode(),
                            result.getTotalCode()))
                    .collect(Collectors.toSet())
            );
            final long costTime5 = System.currentTimeMillis() - startTime5;
            log.info("✅ 输出代码提交量排行榜,耗时: {} 秒", TimeUnit.MILLISECONDS.toSeconds(costTime5));

            // 6. 输出贡献明细
            log.info("输出各研发代码贡献明细:");
            final long startTime6 = System.currentTimeMillis();
            log.info("==========================================>各研发代码贡献明细<==========================================");
            //内容太多，控制台显示不全，输出到文件
            //log.info("成都研发部开发人员贡献明细: {}", JSON.toJSONString(results));
            //定义输出文件路径
            final String filePath2 = Constants.OUTPUT_DIR + DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss") + "贡献明细.json";
            ListToJsonFileWithJackson.printStringToFile(filePath2, JSON.toJSONString(results));
            final long costTime6 = System.currentTimeMillis() - startTime6;
            log.info("✅ 输出各研发代码贡献明细,耗时: {} 秒", TimeUnit.MILLISECONDS.toSeconds(costTime6));


            log.info("✅ 全部运行成功");
        } catch (Exception e) {
            e.printStackTrace();
            log.error("❌ 失败: ", e);
            throw e;
        } finally {
            long costTime = System.currentTimeMillis() - startTime;
            log.info("✅ 运行结束时间: {}", DateUtil.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));
            log.info("✅ 总耗时: {} 秒", TimeUnit.MILLISECONDS.toSeconds(costTime));
        }
    }

    //=== 内部服务类 ===//

    /**
     * 项目相关服务
     */
    @Slf4j
    static class ProjectService {
        private final GitLabApi gitLabAPI;

        public ProjectService(GitLabApi gitLabAPI) {
            this.gitLabAPI = gitLabAPI;
        }

        public List<Project> getAllProjects() throws GitLabApiException {
            return gitLabAPI.getProjectApi().getProjects();
        }

        /**
         * 根据代码提交活动时间范围和项目范围获取项目列表
         *
         * @param projects 项目集合
         * @param since    开始时间
         * @param until    结束时间
         * @return
         * @throws GitLabApiException
         */
        public List<ProjectBo> getProjectsWithCommitsInTimeRange(final List<Project> projects, final Date since, final Date until) throws GitLabApiException, InterruptedException {
            final List<ProjectBo> result = new ArrayList<>();
            for (Project project : projects) {
                final ProjectBo projectBo = this.createProjectBoWithCommits(project, since, until);
                if (!projectBo.getBranchBoList().isEmpty()) {
                    result.add(projectBo);
                }
                Thread.sleep(50);
            }
            return result;
        }

        /**
         * 创建项目 业务对象
         *
         * @param project 项目
         * @param since   开始时间
         * @param until   结束时间
         * @return
         * @throws GitLabApiException
         */
        private ProjectBo createProjectBoWithCommits(final Project project, final Date since, final Date until) throws GitLabApiException, InterruptedException {
            final ProjectBo projectBo = ProjectBo.builder()
                    .projectId(project.getId())
                    .project(project)
                    .since(since)
                    .until(until)
                    .branchBoList(new ArrayList<>())
                    .build();

            final List<String> branchNames = this.getBranches(String.valueOf(project.getId()));

            for (String branchName : branchNames) {
                final BranchBo branchBo = this.createBranchBo(project.getId(), branchName, since, until);
                projectBo.getBranchBoList().add(branchBo);
            }
            return projectBo;
        }

        /**
         * 创建分支 业务对象
         *
         * @param projectId  项目id
         * @param branchName 项目名称
         * @param since      开始时间
         * @param until      结束时间
         * @return
         * @throws GitLabApiException
         */
        private BranchBo createBranchBo(final Integer projectId, final String branchName, final Date since, final Date until) throws GitLabApiException, InterruptedException {
            //final List<Commit> commits = gitLabAPI.getCommitsApi().getCommits(
            //        projectId, branchName, since, until, null, true, true, true
            //);
            Thread.sleep(100);
            final List<Commit> commits = gitLabAPI.getCommitsApi().getCommits(
                    projectId,//以Integer（ID）、String（路径）或project实例的形式对项目进行路径设置
                    branchName,//引用存储库分支或标记的名称，如果没有给出 则默认分支
                    since,//因为只有在此日期之后或当天提交的才会被返回
                    until,//直到只返回此日期之前或当天的提交
                    null,//项目文件的路径
                    true,//全部从存储库中检索每个提交
                    true,//全部从存储库中检索每个提交 关于每次提交的@param with stats统计数据将添加到响应中
                    true//在看到合并提交时仅跟随第一个父提交
            );
            final List<CommitBo> commitBos = commits.stream()
                    .map(commit -> new CommitBo(commit, projectId, branchName, since, until))
                    .collect(Collectors.toList());

            return BranchBo.builder()
                    .branchName(branchName)
                    .commitBoList(commitBos)
                    .since(since)
                    .until(until)
                    .build();
        }

        /**
         * 获取项目的所有分支
         *
         * @param projectId
         * @return
         */
        private List<String> getBranches(final String projectId) throws InterruptedException {
            try {
                Thread.sleep(50);
                return gitLabAPI.getRepositoryApi().getBranches(projectId)
                        .stream()
                        .map(Branch::getName)
                        .collect(Collectors.toList());
            } catch (GitLabApiException e) {
                log.error("❌ 获取项目分支失败: {}", projectId, e);
                return Collections.emptyList();
            }
        }
    }

    /**
     * 用户相关服务
     */
    @Slf4j
    static class UserService {
        private final GitLabApi gitLabAPI;

        public UserService(GitLabApi gitLabAPI) {
            this.gitLabAPI = gitLabAPI;
        }

        /**
         * 获取所有活跃的 开发人员
         *
         * @return
         * @throws GitLabApiException
         */
        public ProjectsAndUsersBo getAllActiveDevelopersAndProjects() throws GitLabApiException, InterruptedException {
            final Set<User> allUsers = new LinkedHashSet<>();

            // 从所有组中获取开发者
            final long startTime1 = System.currentTimeMillis();
            final List<Group> allGroups = gitLabAPI.getGroupApi().getGroups();
            for (Group group : allGroups) {
                final List<Member> groupMembers = gitLabAPI.getGroupApi().getMembers(group.getId());
                this.addMembersToUserSet(allUsers, groupMembers);
            }
            final long costTime1 = System.currentTimeMillis() - startTime1;
            log.info("✅ 从所有组中获取开发者，共计：{} 名,耗时: {} 秒", allUsers.size(), TimeUnit.MILLISECONDS.toSeconds(costTime1));

            // 从所有项目中获取开发者
            final long startTime2 = System.currentTimeMillis();
            //final List<Project> allProjects = gitLabAPI.getProjectApi().getProjects();
            final List<Project> allProjects = projectService.getAllProjects();
            for (Project project : allProjects) {
                final List<Member> projectMembers = gitLabAPI.getProjectApi().getMembers(project.getId());
                this.addMembersToUserSet(allUsers, projectMembers);
            }
            final long costTime2 = System.currentTimeMillis() - startTime2;
            log.info("✅ 从所有项目中获取开发者，共计：{} 名,耗时: {} 秒", allUsers.size(), TimeUnit.MILLISECONDS.toSeconds(costTime2));

            //减少IO
            return ProjectsAndUsersBo.builder()
                    .allProjects(allProjects)
                    .allUsers(new ArrayList<>(allUsers))
                    .build();
        }

        /**
         * 去重添加成员到用户集合
         *
         * @param userSet
         * @param members
         * @throws GitLabApiException
         */
        private void addMembersToUserSet(final Set<User> userSet, final List<Member> members) throws GitLabApiException, InterruptedException {
            for (final Member member : members) {
                Thread.sleep(50);
                final User user = gitLabAPI.getUserApi().getUser(member.getId());
                if (userSet.stream().anyMatch(e -> Objects.equals(e.getId(), user.getId()))) {
                    //存在则跳过
                    continue;
                }
                userSet.add(user);
            }
        }

        /**
         * 遍历筛选 只保留成都研发部的开发者
         *
         * @param allUsers
         * @param cdGroupMembers
         * @return
         */
        public List<User> filterChengduDevelopers(final List<User> allUsers, final List<UserBo> cdGroupMembers) {
            return allUsers.stream()
                    .filter(user -> isChengduDeveloper(user, cdGroupMembers))
                    .sorted(Comparator.comparing(User::getId))
                    .collect(Collectors.toList());
        }

        /**
         * 识别成都研发部开发者
         *
         * @param user
         * @param cdGroupMembers
         * @return
         */
        private boolean isChengduDeveloper(final User user, final List<UserBo> cdGroupMembers) {
            return cdGroupMembers.stream().anyMatch(member ->
                    Objects.equals(member.getName(), user.getName()) ||
                            Objects.equals(member.getName(), user.getUsername()) ||
                            //兼顾拼音，解决刘毅和郑礼珂账号问题
                            Objects.equals(member.getNamePinYi(), user.getName()) ||
                            Objects.equals(member.getNamePinYi(), user.getUsername()) ||
                            //邮箱匹配
                            Objects.equals(member.getEmail(), user.getEmail())
            );
        }
    }

    /**
     * 提交统计服务
     */
    @Slf4j
    static class CommitStatsService {
        private final GitLabApi gitLabAPI;

        public CommitStatsService(GitLabApi gitLabAPI) {
            this.gitLabAPI = gitLabAPI;
        }

        /**
         * 计算开发者的代码贡献量
         *
         * @param developer      开发者
         * @param projects       项目集合
         * @param since          开始时间
         * @param until          结束时间
         * @param cdGroupMembers 成都研发部成员集合
         * @return
         */
        public DeveloperContributedCodeLineStatResult calculateDeveloperCodeStats(final User developer,
                                                                                  final List<ProjectBo> projects,
                                                                                  final Date since,
                                                                                  final Date until,
                                                                                  final List<UserBo> cdGroupMembers) {
            //匹配成都研发部成员
            final UserBo userBo = this.findUserBo(developer, cdGroupMembers);
            if (userBo == null) {
                log.warn("未找到开发者信息: {}", developer.getUsername());
                return null;
            }

            //记录结果数据,总新增行数、总删除行数、总贡献行数
            int totalAdditions = 0;
            int totalDeletions = 0;
            int totalChanges = 0;
            final List<ProjectBo> involvedProjects = new ArrayList<>();

            for (final ProjectBo projectBo : projects) {
                //遍历所有项目，寻找该开发人员代码提交记录
                final ProjectContribution projectContribution = this.calculateProjectContribution(developer, userBo, projectBo);
                if (projectContribution.totalChanges > 0) {
                    //有贡献才计算
                    totalAdditions += projectContribution.additions;
                    totalDeletions += projectContribution.deletions;
                    totalChanges += projectContribution.totalChanges;
                    involvedProjects.add(projectContribution.projectBo);
                }
            }
            if (involvedProjects.size() > 0) {
                //记录参与过的项目
                userBo.getInvolvedProjectBoList().addAll(involvedProjects);
            }

            return DeveloperContributedCodeLineStatResult.builder()
                    .since(DateUtil.format(since, "yyyy-MM-dd HH:mm:ss"))
                    .until(DateUtil.format(until, "yyyy-MM-dd HH:mm:ss"))
                    .userBo(userBo)
                    .id(developer.getId())
                    .name(developer.getName())
                    .email(developer.getEmail())
                    .addCode(totalAdditions)
                    .delCode(totalDeletions)
                    .totalCode(totalChanges)
                    .involvedProjectBoList(involvedProjects)
                    .build();
        }

        /**
         * 找到用户信息
         *
         * @param user
         * @param cdGroupMembers
         * @return
         */
        private UserBo findUserBo(final User user, final List<UserBo> cdGroupMembers) {
            for (final UserBo member : cdGroupMembers) {
                if (Objects.equals(member.getName(), user.getName()) ||
                        Objects.equals(member.getNamePinYi(), user.getUsername()) ||
                        Objects.equals(member.getEmail(), user.getEmail())) {
                    return member;
                }
            }
            return null;
        }

        /**
         * 统计项目贡献
         *
         * @param developer
         * @param userBo
         * @param projectBo
         * @return
         */
        private ProjectContribution calculateProjectContribution(final User developer, final UserBo userBo, final ProjectBo projectBo) {
            //记录贡献数据
            int projectAdditions = 0;
            int projectDeletions = 0;
            int projectTotalChange = 0;
            final List<BranchBo> involvedBranches = new ArrayList<>();

            for (final BranchBo branchBo : projectBo.getBranchBoList()) {
                //遍历分支逐个计算
                final BranchContribution branchContribution = this.calculateBranchContribution(developer, userBo, branchBo);
                if (branchContribution.totalChanges > 0) {
                    projectAdditions += branchContribution.additions;
                    projectDeletions += branchContribution.deletions;
                    projectTotalChange += branchContribution.totalChanges;
                    involvedBranches.add(branchContribution.branchBo);
                }
            }

            final ProjectBo involvedProjectBo = ProjectBo.builder()
                    .projectId(projectBo.getProjectId())
                    .project(projectBo.getProject())
                    .branchBoList(involvedBranches)
                    .since(projectBo.getSince())
                    .until(projectBo.getUntil())
                    .build();

            return new ProjectContribution(involvedProjectBo, projectAdditions, projectDeletions, projectTotalChange);
        }

        /**
         * 统计分支贡献
         *
         * @param developer
         * @param userBo
         * @param branchBo
         * @return
         */
        private BranchContribution calculateBranchContribution(final User developer, final UserBo userBo, final BranchBo branchBo) {
            //记录贡献数据
            int branchAdditions = 0;
            int branchDeletions = 0;
            int branchTotalChanges = 0;
            final List<CommitBo> developerCommits = new ArrayList<>();

            for (final CommitBo commitBo : branchBo.getCommitBoList()) {
                //遍历所有分下所有提交
                if (this.isCommitByDeveloper(commitBo.getCommit(), developer, userBo)) {
                    //匹配开发者
                    final Commit detailedCommit = commitBo.getCommit();
                    //try {
                    //    final Commit detailedCommit = gitLabAPI.getCommitsApi().getCommit(
                    //            commitBo.getProjectId(), commitBo.getCommit().getShortId());
                    //    branchAdditions += detailedCommit.getStats().getAdditions();
                    //    branchDeletions += detailedCommit.getStats().getDeletions();
                    //    developerCommits.add(commitBo);
                    //} catch (GitLabApiException e) {
                    //    log.error("❌ 获取提交详情失败: {}", commitBo.getCommit().getShortId(), e);
                    //}
                    branchAdditions += detailedCommit.getStats().getAdditions();
                    branchDeletions += detailedCommit.getStats().getDeletions();
                    branchTotalChanges += detailedCommit.getStats().getTotal();
                    developerCommits.add(commitBo);
                }
            }

            final BranchBo developerBranchBo = BranchBo.builder()
                    .branchName(branchBo.getBranchName())
                    .commitBoList(developerCommits)
                    .since(branchBo.getSince())
                    .until(branchBo.getUntil())
                    .build();

            return new BranchContribution(developerBranchBo, branchAdditions, branchDeletions, branchTotalChanges);
        }

        /**
         * 判断提交是否由开发者提交
         *
         * @param commit
         * @param developer
         * @param userBo
         * @return
         */
        private boolean isCommitByDeveloper(final Commit commit, final User developer, final UserBo userBo) {
            return Objects.equals(developer.getName(), commit.getAuthorName()) ||
                    Objects.equals(developer.getEmail(), commit.getAuthorEmail()) ||
                    Objects.equals(userBo.getNamePinYi(), commit.getAuthorName()) ||
                    Objects.equals(userBo.getEmail(), commit.getAuthorEmail());
        }

        // 内部记录类
        private static class ProjectContribution {
            /**
             * 项目信息
             */
            final ProjectBo projectBo;
            /**
             * 添加行数
             */
            final int additions;
            /**
             * 删除行数
             */

            final int deletions;
            /**
             * 总行数
             */
            final int totalChanges;

            ProjectContribution(final ProjectBo projectBo, final int additions, final int deletions, final int totalChange) {
                this.projectBo = projectBo;
                this.additions = additions;
                this.deletions = deletions;
                this.totalChanges = totalChange;
            }
        }

        private static class BranchContribution {
            /**
             * 分支信息
             */
            final BranchBo branchBo;
            /**
             * 添加行数
             */
            final int additions;
            /**
             * 删除行数
             */
            final int deletions;
            /**
             * 总行数
             */
            final int totalChanges;

            BranchContribution(final BranchBo branchBo, final int additions, final int deletions, final int totalChanges) {
                this.branchBo = branchBo;
                this.additions = additions;
                this.deletions = deletions;
                this.totalChanges = totalChanges;
            }
        }
    }
}